#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<assert.h>
#include<ctype.h>
#include<sys/stat.h>
#include<fcntl.h>


#define NUM 1024
#define SIZE 128
#define SEP " "

#define DROP_SPACE(start) do{ while(isspace(*start)) start++; }while(0)

#define NONE_REDIR -1
#define INPUT_REDIR 0
#define OUTPUT_REDIR 1
#define APPEND_REDIR 2

int g_redir_flags = NONE_REDIR;
char *g_redir_filename = NULL;

char command_line[NUM];
char* command_args[SIZE];

char env_buffer[NUM];// for test 

extern char ** environ;

//对应上层的内建命令
int ChangeDir(const char* new_path)
{
    chdir(new_path);
    return 0;//调用成功
}

void PutEnvInMyShell(char* new_env)
{
    putenv(new_env);
}

void CheckDir(char *commands)
{
    assert(commands);
    //[start,end)
    char *start = commands;
    char *end = commands + strlen(commands);
    while(start < end)
    {
        if(*start == '>')
        {
            if(*(start+1) == '>' )
            {
                //ls -a -l>>log.txt --追加重定向
                *start = '\0';
                start += 2;
                g_redir_flags = APPEND_REDIR;
                DROP_SPACE(start);
                g_redir_filename = start;
                break;
            }
            else 
            {
                //ls -a -l>log.txt -- 输出重定向
                *start = '\0';
                start++;
                g_redir_flags = OUTPUT_REDIR;
                DROP_SPACE(start);
                g_redir_filename = start;
                break;
            }
        }
        else if(*start == '<')
        {
            //输入重定向
            *start = '\0';
            start++;
            g_redir_flags = INPUT_REDIR;
            DROP_SPACE(start);
            g_redir_filename = start;
            break;
        }
        else 
        {
            ++start;
        }
    }
}

int main()
{
    // shell 本质上就是一个死循环
    while(1)
    {
        // 每次执行指令之前初始化部分全局变量
        g_redir_flags = NONE_REDIR;
        g_redir_filename = NULL;

        //1.显示提示符
        printf("[当前用户@主机名 当前目录]$ ");
        fflush(stdout);

        //2.获取用户输入
        memset(command_line,'\0',sizeof(command_line)*sizeof(char));
        fgets(command_line,NUM,stdin); //键盘，标准输入，stdin,获取到的是c风格的字符串，'\0'
        command_line[strlen(command_line)-1] = '\0';//清空'\n'
        //printf("%s\n",command_line);

        //3.ls -a -l>log.txt  or  cat<file.txt  ls -a -l>>log.txt  or  la -a -l
        // ls -a -l>lob.txt -> ls -a -l\0log.txt
        CheckDir(command_line);

        //4."ls -a -l" -> "ls" "-a" "-l" 分割字符串 可用c++中的substr
        command_args[0] = strtok(command_line,SEP);
        int index = 1;
        //给ls命令添加颜色
        if(strcmp(command_args[0],"ls") == 0) command_args[index++] = (char*)"--color=auto";
        //strtok 截取成功，返回字符串的起始地址，截取失败，返回NULL
        while(command_args[index++] = strtok(NULL,SEP));

        //5.TODO(内建命令)
        if(strcmp(command_args[0],"cd")==0&&command_args[1]!=NULL)
        {
            ChangeDir(command_args[1]); //让调用放进行路径切换，父进程
            continue;
        }
        if(strcmp(command_args[0],"export")==0&&command_args[1]!=NULL)
        {
            //目前，环境变量信息在command_line，会被清空
            //此处我们需要自己保存一下环境变量内容
            strcpy(env_buffer,command_args[1]);
            PutEnvInMyShell(env_buffer);
            continue;
        }

        //6.创建进程，执行
        pid_t id = fork();
        if(id == 0)
        {
            int fd = -1;
            switch(g_redir_flags)
            {
                case NONE_REDIR:
                    break;
                case INPUT_REDIR:
                    fd = open(g_redir_filename,O_RDONLY);
                    dup2(fd,0);
                    break; 
                case OUTPUT_REDIR:
                    fd = open(g_redir_filename,O_WRONLY | O_CREAT | O_TRUNC);
                    dup2(fd,1);
                    break;
                case APPEND_REDIR:
                    fd = open(g_redir_filename,O_WRONLY | O_CREAT | O_APPEND);
                    dup2(fd,1);
                    break;
                default:
                    printf("bug?\n");
                    break;
            }

            //7.程序替换,会影响曾经子进程打开的文件吗？-> 不会
            execvp(command_args[0],command_args);
            exit(1);//执行到这里，子进程替换失败
        }
        int status = 0;
        pid_t ret = waitpid(id,&status,0);
        if(ret > 0)
        {
            //printf("等待子进程成功: sig = %d , code = %d\n",status&0x7F,(status>>8)&0xFF);
        }
    }
    return 0;
}
